package org.infoset.xml.memory;

import java.util.*;
import java.io.*;
import java.net.*;

import org.infoset.xml.*;
import org.infoset.xml.util.SiblingIterator;
import org.infoset.xml.util.XPathUtils;


public class MemoryElement extends MemoryParent implements Element {
   
   static Name xmlSpace = InfosetFactory.getDefaultInfoset().createName(Name.XML_NAMESPACE_URI,"space");
   static URI xmlSchemaNamespace;
   static Name anySimpleTypeName;
   static TypeDefinition anySimpleType;
   static Name anyTypeName;
   static TypeDefinition anyType;
   static {
      try {
         xmlSchemaNamespace = InfosetFactory.getDefaultInfoset().createNamespace("http://www.w3.org/2001/XMLSchema");
         anyTypeName = InfosetFactory.getDefaultInfoset().createName(xmlSchemaNamespace, "anyType");
         anyType = new MemoryTypeDefinition(anyTypeName);
         anySimpleTypeName = InfosetFactory.getDefaultInfoset().createName(xmlSchemaNamespace, "anySimpleType");
         anySimpleType = new MemoryTypeDefinition(anySimpleTypeName);
      } catch (Exception ex) {
         ex.printStackTrace();
      }
   }
   
   class NoPutHashMap extends HashMap<Name,Attribute> {
      public Attribute put(Name name, Attribute att) {
         throw new UnsupportedOperationException("put() not allowed on attribute map.");
      }
      
      void localPut(Name name,Attribute att) {
         super.put(name,att);
      }
   }

   // TODO: this will fail for generateId!!!
   class TempNamespace implements Namespace {
      
      String prefix;
      URI namespace;
      Element parent;
      TempNamespace(Element parent,String prefix) {
         this.parent = parent;
         this.prefix = prefix;
         this.namespace = scope.getNamespace(prefix);
      }
      
      public Item copyOfItem(boolean copyAll) {
         return new TempNamespace(parent,prefix);
      }
      
      public int compareTo(Item other) {
         if (other instanceof Namespace) {
            Namespace n = (Namespace)other;
            int cmp = n.getPrefix().compareTo(prefix); 
            if (cmp==0) {
               cmp = n.getNamespaceName().compareTo(namespace);
            }
            return cmp;
         } else {
            return 0;
         }
      }
      
      public Iterator getAxis(Infoset.Axis type) {
         throw new UnsupportedOperationException("Not implemented.");
      }
      
      public Document getDocument() {
         return MemoryElement.this.getDocument();
      }
      
      public String getGeneratedId() {
         return "h"+hashCode();
      }
      
      public URI getNamespaceName() {
         return namespace;
      }
      
      public Parent getOrigin() {
         return parent;
      }
      
      public Infoset getInfoset() {
         return parent.getInfoset();
      }
      
      public String getPrefix() {
         return prefix;
      }
      
      public ItemType getType() {
         return ItemType.NamespaceItem;
      }
      
   }
   
   class NamespaceIterator implements Iterator {

      Element parent;
      Iterator prefixes;
      NamespaceIterator(Element parent) {
         prefixes = scope.getPrefixes();
      }
      public boolean hasNext() {
         return prefixes.hasNext();
      }
      
      public Object next() {
         String prefix = (String)prefixes.next();
         return new TempNamespace(parent,prefix);
      }
      
      public void remove() {
         throw new UnsupportedOperationException("Cannot remove namespace through this iterator.");
      }
      
   }
   
   protected Name name;
   protected String prefix;
   protected NoPutHashMap attributes;
   protected NamespaceScope scope;
   protected int index;
   protected Map<String,URI> nsProxy;
   protected int line;
   protected int column;
   protected TypeDefinition typeDefinition;
   protected Name typeName;
   protected int validity;
   protected int validationAttempted;
   protected List errors;

   public MemoryElement(Infoset infoset,URI base,Parent origin,Name name) {
      super(infoset,0,base,origin);
      this.name = name;
      this.prefix = Name.NO_PREFIX;
      this.attributes = null;
      this.nsProxy = null;
      this.line = 0;
      this.column = 0;
      if (origin==null || origin instanceof Document) {
         scope = NamespaceScope.getDefaultScope();
      } else {
         scope = ((Element)origin).getNamespaceScope();
      }
      this.index = 0;
      this.validity = NOT_KNOWN;
      this.validationAttempted = ATTEMPTED_NONE;
      this.typeDefinition = anyType;
      this.typeName = anyType.getName();
      this.errors = null;
   }

   public MemoryElement(Infoset infoset,URI base,Name name,NamespaceScope scope) {
      super(infoset,0,base,null);
      this.name = name;
      this.prefix = Name.NO_PREFIX;
      this.attributes = null;
      this.nsProxy = null;
      this.scope = scope;
      this.index = 0;
      this.line = 0;
      this.column = 0;
      if (scope==null) {
         throw new IllegalArgumentException("NamespaceScope cannot be null.");
      }
      this.validity = NOT_KNOWN;
      this.validationAttempted = ATTEMPTED_NONE;
      this.typeDefinition = anyType;
      this.errors = null;
   }

   private MemoryElement(Infoset infoset,URI base,Parent origin,Name name,Map<String,URI> nsProxy,NamespaceScope scope) {
      super(infoset,0,base,origin);
      this.name = name;
      this.prefix = Name.NO_PREFIX;
      this.attributes = null;
      this.nsProxy = nsProxy;
      this.scope = scope;
      this.line = 0;
      this.column = 0;
      if (scope==null) {
         throw new IllegalArgumentException("NamespaceScope cannot be null.");
      }
      this.index = 0;
      this.validity = NOT_KNOWN;
      this.validationAttempted = ATTEMPTED_NONE;
      this.typeDefinition = anyType;
      this.errors = null;
   }


   /*
   public Object clone() {
      return cloneMe(null);
   }

   Element cloneMe(Parent parent) {
      // TODO: need to clone scope
      MemoryElement e = new MemoryElement(infoset,baseURI,parent,name,nsProxy,scope);

      e.setPrefix(prefix);
      
      // copy attributes
      if (attributes!=null) {
         Iterator attNames = attributes.keySet().iterator();
         while (attNames.hasNext()) {
            Name attName = (Name)attNames.next();
            Attribute att = (Attribute)attributes.get(attName);
            e.setAttribute(attName,att.getText());
         }
      }
      cloneChildren(e);
      return e;
   }
    */

   protected void setIndex(int index) { 
      this.index = index;
   }

   public ItemType getType() {
      return ItemType.ElementItem;
   }

   public Name getName() {
      return name;
   }
  
   public void setName(Name value) {
      name = value;
   }  

   public void setName(String localName) {
      this.name = infoset.createName(localName);
   }

   public void setName(URI namespaceName,String localName) {
      this.name = infoset.createName(namespaceName,localName);
   }

   public String getPrefix() {
      return prefix;
   }
   
   public void setPrefix(String prefix) {
      if (prefix==null) {
         throw new IllegalArgumentException("Prefix cannot be a null value.");
      }
      this.prefix = prefix;
   }
   
   public boolean hasAttributes() {
      return attributes!=null;
   }

   public Map<Name,Attribute> getAttributes() {
      if (attributes==null) {
         return Collections.emptyMap();
      } else {
         return attributes;
      }
   }

   public String getAttributeValue(Name name) {
      if (attributes==null) {
         return null;
      }
      Attribute att = attributes.get(name);
      return att==null ? null : att.getText();
   }

   public String getAttributeValue(String name) {
      if (attributes==null) {
         return null;
      }
      Attribute att = attributes.get(Name.create(name));
      return att==null ? null : att.getText();
   }

   public Attribute setAttributeValue(Name name,String value) {
      if (attributes==null) {
         attributes = new NoPutHashMap();
      }
      if (value==null) {
         throw new IllegalArgumentException("Null values for attributes are not allowed.");
      }
      Attribute att = new MemoryAttribute(infoset,this,name,value,null);
      attributes.localPut(name,att);
      if (name.equals(Attribute.XML_BASE)) {
         URI base = getBaseURI();
         URI newBase = base!=null ? base.resolve(value) : URI.create(value);
         setBaseURI(newBase,false);
      }
      return att;
   }

   public Attribute setAttributeValue(String local,String value) {
      if (attributes==null) {
         attributes = new NoPutHashMap();
      }
      
      if (value==null) {
         throw new IllegalArgumentException("Null values for attributes are not allowed.");
      }
      
      Name aname = infoset.createName(local);
      Attribute att = new MemoryAttribute(infoset,this,aname,value,null);
      attributes.localPut(aname,att);
      return att;
   }

   public Element addElement(Name name) {
      Element child = new MemoryElement(infoset,null,this,name);
      add(child);
      return child;
   }

   public Element addElement(String local) {
      Element child = new MemoryElement(infoset,null,this,infoset.createName(local));
      add(child);
      return child;
   }

   public Element addElement(int index,Name name) {
      Element child = new MemoryElement(infoset,null,this,name);
      add(index,child);
      return child;
   }

   public Element addElement(int index,String local) {
      Element child = new MemoryElement(infoset,null,this,infoset.createName(local));
      add(index,child);
      return child;
   }

   public Characters addData(Object data) {
      Characters child = new MemoryCharacters(infoset,this,data.toString(),data);
      add(child);
      return child;
   }

   public Characters addCharacters(String data) {
      if (data==null) {
         throw new IllegalArgumentException("Null values are not allowed.");
      }
      Characters child = new MemoryCharacters(infoset,this,data,null);
      add(child);
      return child;
   }

   public Child addData(int index,Object data) {
      Child child = new MemoryCharacters(infoset,this,data.toString(),data);
      add(index,child);
      return child;
   }

   public Child addCharacters(int index,String value) {
      if (value==null) {
         throw new IllegalArgumentException("Null values are not allowed.");
      }
      Child child = new MemoryCharacters(infoset,this,value,null);
      add(index,child);
      return child;
   }

   public NamespaceScope getNamespaceScope() { 
      return scope;
   }

   public boolean hasNamespaceDeclarations() {
      return nsProxy!=null;
   }
   
   public Child getFollowingSibling() {
      if (origin==null) {
         return null;
         //throw new IllegalStateException("Cannot get following sibling while processing events.");
      }
      return origin.get(index+1);
   }

   public Child getPrecedingSibling() {
      if (origin==null) {
         return null;
         //throw new IllegalStateException("Cannot get previous sibling while processing events.");
      }
      return origin.get(index-1);
   }
   
   void setOrigin(Parent origin) {
      super.setOrigin(origin);
      if (origin instanceof Element) {
         Element parentElement = (Element)origin;
         if (nsProxy==null) {
            scope = parentElement.getNamespaceScope();
         } else {
            scope = new NamespaceScope(parentElement.getNamespaceScope(),nsProxy);
         }
      }
   }

   public boolean isSpacePreserving() {
      Attribute space = (Attribute)attributes.get(xmlSpace);
      if (space==null) {
         Parent p = getOrigin();
         if (p==null) { 
            return true;
         }
         return (p instanceof Element) ? ((Element)p).isSpacePreserving() : false;
      } else {
         return space.getText().equals("preserve");
      }
   }

   /** Sets the default namespace locally.
    * @param namespace The namespace name to be set as default.
    */
   public void addNamespaceBinding(String prefix, URI namespace) {
      if (nsProxy==null) {
         NamespaceScope parentScope = scope;
         nsProxy = new HashMap<String,URI>();
         scope = new NamespaceScope(parentScope,nsProxy);
         for (Child child : this) {
            if (child.getType()==ItemType.ElementItem) {
               ((MemoryElement)child).setParentScope(scope);
            }
         }
      } 
      nsProxy.put(prefix,namespace);
   }   
   
   void setParentScope(NamespaceScope parentScope) {
      if (nsProxy==null) {
         scope = parentScope;
      } else {
         scope = new NamespaceScope(parentScope,nsProxy);
      }
   }

   public void localizeNamespaceDeclarations() {
      NamespaceScope currentScope = nsProxy==null ? getNamespaceScope() : getNamespaceScope().getParent();
      while (currentScope!=null) {
         Map<String,URI> proxy = currentScope.getDataProxy();
         if (proxy!=NamespaceScope.getDefaultPrefixes()) {
            for (String prefix : proxy.keySet()) {
               if (nsProxy==null || nsProxy.get(prefix)==null) {
                  URI ns = proxy.get(prefix);
                  addNamespaceBinding(prefix,ns);
               }
            }
         }
         currentScope = currentScope.getParent();
      }
      /*
      for (Iterator toCheck = getChildren(); toCheck.hasNext(); ) {
         Child c = (Child)toCheck.next();
         if (c instanceof Element) {
            Element e = (Element)c;
            if (e.getNamespaceScope()!=scope) {
               if (e.getNamespaceScope().getParent()!=scope) {
                  e.setParentScope(scope);
               }
            }
         }
      }*/
   }   
   
   
   public Iterator getAxis(Infoset.Axis type) {
      if (type==Infoset.Axis.AttributeAxis) {
         return attributes.values().iterator();
      } else {
         return super.getAxis(type);
      }
   }

   public Iterator getFollowingSiblings() {
      return new SiblingIterator(getFollowingSibling(),true);
   }
   
   public Iterator getPrecedingSiblings() {
      return new SiblingIterator(getPrecedingSibling(),true);
   }
   
   public Iterator getInScopeNamespaces() {
      return new NamespaceIterator(this);
   }
   
   public int getColumn() {
      return column;
   }   

   public int getLine() {
      return line;
   }
   
   public void setColumn(int column) {
      this.column = column;
   }
   
   public void setLine(int line) {
      this.line = line;
   }
   
   public String getText() {
      switch (size()) {
         case 0:
            return "";
         case 1: {
            
            Child c = get(0);
            return c instanceof Characters ? ((Characters)c).getText() : "";
         }
         default: {
            StringBuffer value = new StringBuffer();
            for (Child c : this) {
               if (c instanceof Characters) {
                  value.append(((Characters)c).getText());
               }
            }
            return value.toString();
         }
      }
   }
   
   public String getNormalizedText() {
      switch (size()) {
         case 0:
            return "";
         case 1: {
            
            Child c = get(0);
            return c instanceof Characters ? ((Characters)c).getText().trim() : "";
         }
         default: {
            StringBuffer value = new StringBuffer();
            for (Child c : this) {
               if (c instanceof Characters) {
                  value.append(((Characters)c).getText());
               }
            }
            return value.toString().trim();
         }
      }
   }
   
   public int getValidity() {
      return validity;
   }
   
   public int getValidationAttempted() {
      return validationAttempted;
   }
   
   public void setValidity(int validity, int attempted) {
      this.validity = validity;
      this.validationAttempted = attempted;
   }
   
   public TypeDefinition getTypeDefinition() {
      return typeDefinition;
   }
   
   public void setTypeDefinition(TypeDefinition typeDef) {
      typeDefinition = typeDef;
      typeName = typeDef==null ? typeDef.getName() : null;
   }
   
   public Iterator getErrors() {
      return errors==null ? Collections.EMPTY_LIST.iterator() : errors.iterator();
   }

   public void setErrors(List errorStringList)
   {
      errors = new ArrayList();
      errors.addAll(errorStringList);
   }
   
   public void addError(String error) {
      if (errors==null) {
         errors = new ArrayList();
      }
      errors.add(error);
   }
   
   public String toString() {
      return "[child "+name+" at "+index+"]";
   }
   
   public Element getFirstElementNamed(Name name) {
      int len = size();
      for (int i=0; i<len; i++) {
         Item child = get(i);
         if (child.getType()==Item.ItemType.ElementItem && ((Element)child).getName().equals(name)) {
            return (Element)child;
         }
      }
      return null;
   }
   
   public Iterator<Element> getElementsByName(final Name name) {
      return new Iterator<Element>() {
         int index = 0;
         int size = size();
         Element next = null;
         Element last = null;
         public void remove() {
            if (last!=null) {
              index--;
              size--;
              MemoryElement.this.remove(index);
            } else {
               throw new IllegalStateException("There is no current element to remove.");
            }
         }
         public boolean hasNext() {
            if (next==null && index<size) {
               while (next==null && index<size) {
                  Child c = get(index);
                  if (c.getType()==ItemType.ElementItem && ((Named)c).getName().equals(name)) {
                     next = (Element)c;
                  }
                  index++;
               }
            }
            return next!=null;
         }
         public Element next() {
            last = next;
            next = null;
            return last;
         }
      };
      /*
      List<Element> result = new ArrayList<Element>();
      int len = size();
      for (int i=0; i<len; i++) {
         Item child = get(i);
         if (child.getType()==Item.ItemType.ElementItem && ((Element)child).getName().equals(name)) {
            result.add((Element)child);
         }
      }
      return result.iterator();
       */
   }
   
   public Iterator<Element> getElementChildren() {
      return new Iterator<Element>() {
         int index = 0;
         int size = size();
         Element next = null;
         Element last = null;
         public void remove() {
            if (last!=null) {
              index--;
              MemoryElement.this.remove(index);
            } else {
               throw new IllegalStateException("There is no current element to remove.");
            }
         }
         public boolean hasNext() {
            if (next==null && index<size) {
               while (next==null && index<size) {
                  Child c = get(index);
                  if (c.getType()==ItemType.ElementItem) {
                     next = (Element)c;
                  }
                  index++;
               }
            }
            return next!=null;
         }
         public Element next() {
            last = next;
            next = null;
            return last;
         }
      };
   }
   public Item copyOfItem(boolean copyAll) {
      MemoryElement e = new MemoryElement(infoset,baseURI,null,name);
      for (Name attName : getAttributes().keySet()) {
         e.setAttributeValue(attName,getAttributeValue(attName));
      }
      if (nsProxy!=null) {
         for (String prefix : nsProxy.keySet()) {
            e.addNamespaceBinding(prefix,nsProxy.get(prefix));
         }
      }
      if (copyAll) {
         for (Child c : this) {
            e.add((Child)c.copyOfItem(copyAll));
         }
      }
      return e;
   }
   
   public Name getTypeName() {
      return typeName;
   }
   
   public void setTypeName(Name typeName) {
      this.typeDefinition = null;
      this.typeName = typeName;
   }
   
   
   
}

